﻿using System.Collections.Generic;
using System.Linq;
using Microsoft.Ccr.Core;
using Microsoft.Ccr.Core.Arbiters;

namespace CcrFlows.Internal.Stages
{
    internal abstract class StageBase
    {
        protected readonly DispatcherQueue taskQueue = new DispatcherQueue();
        protected ReceiverTask inputHandler;

        protected readonly IList<IPort> inputPorts;
        protected readonly IList<IPort> outputPorts;

        private readonly StageBase[] nextStages;


        protected StageBase(IEnumerable<IPort> inputPorts, IEnumerable<IPort> outputPorts)
        {
            this.inputPorts = new List<IPort>(inputPorts);
            this.outputPorts = new List<IPort>(outputPorts);
            this.nextStages = new StageBase[outputPorts.Count()];
        }


        protected void InitializeInputHandler(ReceiverTask inputHandler)
        {
            if (this.inputHandler != null) this.inputHandler.Cleanup();
            this.inputHandler = inputHandler;

            WaitForNextInput();
        }

        protected void WaitForNextInput()
        {
            Arbiter.Activate(
                this.taskQueue,
                this.inputHandler
                );
        }


        public void ContinueWith(int outputIndex, StageBase nextStage)
        {
            ContinueWith(outputIndex, nextStage, 0);
        }

        public void ContinueWith(int outputIndex, StageBase nextStage, int inputIndex)
        {
            this.outputPorts[outputIndex] = nextStage.Input.ElementAt(inputIndex);
            this.nextStages[outputIndex] = nextStage;
        }


        public IEnumerable<IPort> Input
        {
            get
            {
                return this.inputPorts;
            }
        }

        public IEnumerable<IPort> Output
        {
            get
            {
                return this.outputPorts;
            }
        }


        public IEnumerable<StageBase> Next
        {
            get { return this.nextStages; }
        }


        public IEnumerable<StageBase> TransitiveHull
        {
            get
            {
                Dictionary<StageBase, object> stagesVisited = new Dictionary<StageBase, object>();
                return new List<StageBase>(CalculateTransitiveHull(this, stagesVisited));
            }
        }

        public IEnumerable<StageBase> CalculateTransitiveHull(StageBase stage, Dictionary<StageBase, object> stagesVisited)
        {
            if (stagesVisited.ContainsKey(stage)) yield break;
            stagesVisited.Add(stage, null);

            // Don´t use Linq .Where() since it returns a follower although nextStages contains only null values
            IList<StageBase> followers = new List<StageBase>();
            foreach (StageBase follower in stage.nextStages)
                if (follower != null) followers.Add(follower);

            if (followers.Count > 0)
            {
                foreach (StageBase child in followers)
                {
                    foreach (StageBase grandChild in CalculateTransitiveHull(child, stagesVisited))
                        yield return grandChild;
                }
            }
            else
                yield return stage;
        }
    }
}
